#include <fstream>
#include <sstream>
#include <iterator>
#include <vector>

#include <functional>
#include <tr1/memory>
#include <boost/bind.hpp>

#include <ftw.h>
#include <errno.h>
#include <unistd.h>

#include "page_info.h"
#include "page_db.h"
#include "page_type.h"
#include "junix.h"

namespace jsearch
{

// save the name list of all files to index.
std::vector<std::string> page_name_db;
int ftwf(const char *fpath,const struct stat *sb,int flags );

struct page_db::Impl
{
	public:
		Impl( std::string scan_dir, std::string file )
			:db_file(file),page_dir(scan_dir),next_id(0)
		{
			if (scan_dir.empty() )
				page_dir="./";

			if (!scan_dir.empty() && scan_dir.at(0) != '/')	{
				char cwd[BUFSIZ];
				char ncwd[BUFSIZ];
				if (getcwd(cwd,BUFSIZ) == NULL)
					err_sys("getcwd");

				if (chdir(scan_dir.c_str()) == -1)
					err_sys("chdir");

				if (getcwd(ncwd,BUFSIZ) == NULL)
					err_sys("getcwd");

				if (chdir(cwd) == -1)
					err_sys("chdir");

				std::string wd(ncwd);
				page_dir=wd;
			}
		}

		//for indexer
		bool create_db()
		{
			clear();

			if (!scan_dir())
				return false;

			if (page_name_db.empty()) {
				fprintf(stderr, "No file was found in the scan dir.\n");
				truncate(db_file.c_str(),0);
				return false;
			}

			int size=page_name_db.size();
			for (int i=0;i<size;i++) {
				page_info pi(i,PAGE_TYPE_ASCII,page_name_db.at(i) );
				pages.push_back(pi);
			}

			if (persist()) {
				return true;
			} else {
				return false;
			}
		}

		bool persist()
		{
			unlink(db_file.c_str());

			int size=pages.size();
			std::ofstream of(db_file.c_str());
			for (int i=0;i<size;i++) {
				of<<pages.at(i);
			}

			if( of.fail()) {
				std::cerr<<"file format error"<<std::endl;
				of.clear();
				return false;
			}

			of.close();

			return true;
		}

		bool restore()
		{
			clear();

			//std::cout<<db_file<<std::endl;
			std::ifstream in(db_file.c_str());
			if (!in) {
				fprintf(stderr,"error:Page DB file open error\n");
				return false;
			}

			std::string line;
			while (getline(in,line)) {
				pages.push_back( page_info(line) );
			}

			if (in.bad()) {
				fprintf(stderr, "error:Page DB file read error\n");
				in.clear();
				return false;
			}

			in.close();

			return true;
		}

		// db interface for user
		std::string getnamebyid(int id)
		{
			if(id<0 || id>=size())
				return std::string("");

			return pages.at(id).name();
		}

		int getidbyname(const std::string path)
		{

			if (path.empty())
				return -1;

			using boost::bind;

			std::vector<page_info>::iterator pos;
			pos=std::find_if(pages.begin(),pages.end(),
					bind( std::equal_to<std::string>(),
						  bind(&page_info::name, _1), path));

			if (pos != pages.end())
				return pos->id();

			return -1;
		}

		bool empty() const
		{
			return pages.empty();
		}

		int size() const
		{
			return pages.size();
		}

		//for debug
		void print_db(std::ostream& os = std::cout) const
		{
			std::copy(pages.begin(),pages.end(),
					std::ostream_iterator<page_info>(os));
		}

	private:
		std::vector<page_info> pages;
		std::string db_file; // file name of db
		std::string page_dir; // which dir to scan for indexing
		int next_id; //unused

	private:
		bool scan_dir()
		{
			if (page_dir.empty()) return false;

			page_name_db.clear();
			if (ftw(page_dir.c_str(),ftwf,0) < 0) {
				perror("ftw");
				exit(1);
			}

			return true;
		}

		bool clear()
		{
			next_id=0;
			pages.clear();
			page_name_db.clear();

			return true;
		}


};

page_db::page_db(std::string scan_dir, std::string dbf)
:pimpl(new Impl(scan_dir, dbf))
{ }

bool page_db::create_db()
{
	return pimpl->create_db();
}

bool page_db::persist()
{
	return pimpl->persist();
}

bool page_db::restore()
{
	return pimpl->restore();
}

std::string page_db::getnamebyid(int id)
{
	return pimpl->getnamebyid(id);
}

int page_db::getidbyname(const std::string path )
{
	return pimpl->getidbyname(path);
}

void page_db::print_db(std::ostream& os) const
{
	pimpl->print_db(os);
}

bool page_db::empty() const
{
	return pimpl->empty();
}

int page_db::size() const
{
	return pimpl->size();
}



int ftwf (const char *fpath, const struct stat *sb, int flags)
{
	if (flags == FTW_F) {
		if (page_type(fpath)==PAGE_TYPE_ASCII)
			page_name_db.push_back( std::string(fpath));
	}

	return 0;
}

}//end namespace
